/*
 * Title: WADS
 * Description: Web-based Auto flow Documents System
 * Copyright: 2007
 * Company: CoE
 * @author: Old developer team.
 * @version 1.1
 */

package wads.drawing.node;

import wads.drawing.line.CaseLink;
import wads.drawing.CaseShape;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphMetrics;
import java.awt.font.GlyphVector;
import java.awt.font.LineMetrics;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.StringTokenizer;
import java.util.Vector;
import wads.app.CaseEditor;
import wads.app.DrawPanel;
import wads.app.SelectionHandle;
//import javax.swing.JOptionPane;

public abstract class CaseNode extends CaseShape implements Cloneable, Serializable {
//  private transient CaseNodeDialog nodeDialog;
  //instance to be created in subclass
  private String content[];
  protected String panelNames[]; // names for generating documentation

  private Vector<CaseLink> connectedLinks = new Vector<CaseLink>();
  private transient SelectionHandle selectionHandles[];
  public static final int ALIGN_LEFT = 0;
  public static final int CENTER = 1;
  public static final int ALIGN_RIGHT = 2;
  private int currentAlignment =  CENTER; //default alignment
  public static final int DEFAULT_NODE_ICON_WIDTH = 32;
  public static final int DEFAULT_NODE_ICON_HEIGHT = 32;

  private Point startAnchor; // used for initial drawing and resizing
  private Point moveAnchor; // used for moving
  private int x = 0;
  private int y = 0; //upper right corner of node
  private int width = 0; //width of node
  private int height = 0; //height of node

  //buffer pixel distance from actual shape
  private int boundsDX = 2;
  private int boundsDY = 2;

//  public abstract CaseNodeDialog createDialog();

  public CaseNode() {
    super();
    if(false)
      System.out.println(startAnchor+" "+moveAnchor);
  }

  public CaseNode(String name) {
    super();
    setName(name);
  }

  public CaseNode(Point anchor, String name) {
    super();
    setAnchorPoint(anchor);
    setName(name);
  }

//  public CaseNode(Point anchor, String name, CaseNodeDialog dialog) {
//    super();
//    setAnchorPoint(anchor);
//    setName(name);
//    nodeDialog = dialog;
//  }
  
  public Point getAnchorPoint() {
    return startAnchor;
  }
  
  public void setAnchorPoint(Point p) {
    startAnchor = p;
    x = p.x;
    y = p.y;
  }

  public void setNodeShapeBounds2D(int x1, int y1, int width, int height) {
    //Construct bounds about origin
    setShapeBounds2D(new Rectangle2D.Float(x1-boundsDX, y1 -boundsDY, width+2*boundsDX, height+2*boundsDY));
  }
  
  public void setStartX(int sx){
    x = sx;
  }
  
  public int getStartX(){
    return x;
  }

  public void setStartY(int sy){
    y = sy;
  }
  public int getStartY(){
    return y;
  }

  public int getEndX(){
    return x + width;
  }

  public int getEndY(){
    return y + height;
  }
  
  public void setHeight(int h){
    height = h;
  }

  public int getHeight(){
    return height;
  }
  
  public void setWidth(int w){
    width = w;
  }

  public int getWidth(){
    return width;
  }

  public Dimension getDimension(){
    return new Dimension(width, height);
  }

  public void setDimension(int w, int h){
    width = w;
    height = h;
  }

  public Point getCentriod(){
    int cx = Math.round(x + width/2.0f);
    int cy = Math.round(y + height/2.0f);
    return new Point(cx,cy);
  }

  public Vector<CaseLink> getConnectedLinks(){
    return connectedLinks;
  }

  public void setConenctedLinks(Vector<CaseLink> connectedLinks){
    if (connectedLinks != null)
      this.connectedLinks = connectedLinks;
  }

  public CaseLink getConnectedLink(int i){
    if (connectedLinks.size() >= i)
      return (CaseLink)connectedLinks.elementAt(i);
    else
      return null;
  }

  public void addConnectedLink(CaseLink link){
    //Add the new link if the link is not null and it is
    //not already in the vector
    if ((link != null)&&(!connectedLinks.contains(link)))
      connectedLinks.addElement(link);
  }

  public int getConnectedLinksSize(){
    return connectedLinks.size();
  }

  protected void removeConnectedLink(CaseLink link){
    connectedLinks.removeElement(link);
  }

  protected void removeAllConnectedLinks(){
    connectedLinks.removeAllElements();
  }

  public void linkRemoved(CaseLink link){
    removeConnectedLink(link);
  }

  //Refresh links attached to this node.
  //Used : When node is resized after text is added or removed
  public void refreshLinks(){
    for (int i=0;i<connectedLinks.size();i++){
      CaseLink link = (CaseLink)connectedLinks.elementAt(i);
      link.refresh();
    }
  }

    @Override
  public void move(DrawPanel drawPanel, Point p){
    //Update node new location
    this.x = p.x;
    this.y = p.y;

    //Refresh all its links
    for (int i=0; i< connectedLinks.size(); i++){
      CaseLink curLink = (CaseLink)connectedLinks.elementAt(i);
      curLink.refresh();
    }
  }

  public void moveBy(int dx, int dy){
    //Update nodes new location
    this.x = this.x + dx;
    this.y = this.y + dy;

    //Refresh all its links
    for (int i=0; i< connectedLinks.size(); i++){
      CaseLink curLink = (CaseLink)connectedLinks.elementAt(i);
      curLink.refresh();
    }
  }

  /** Note : Need to implement all these quick to get the node's
   *  functionality working
   */

    @Override
  public void resize(DrawPanel drawPanel, Point p)
  {System.out.println("resize : Not yet implemented");}

    @Override
  public void resize(DrawPanel drawPanel, Point p, Cursor cursor)
  {System.out.println("resize II: Not yet implemented");}

    @Override
  public void setMoveAnchor(Point p)
  {System.out.println("setMoveAnchor : Not yet implemented");}
  
    @Override
  public void highlight(DrawPanel drawPanel)
  {System.out.println("highlight : Not yet implemented");}

    @Override
  public void highlight(Graphics g){
    // draw selection handles
    int iNumHandles = 4;
    int iHandleSize = 5;
    int iHandleOffset = (int)((iHandleSize - 1) / 2);
    
    // put handles at four corners
    selectionHandles = new SelectionHandle[iNumHandles];
    
    selectionHandles[0] = new SelectionHandle(x-iHandleOffset, y-iHandleOffset,
        iHandleSize, iHandleSize,
        new Cursor(Cursor.NW_RESIZE_CURSOR));
    
    selectionHandles[1] = new SelectionHandle(x+width - iHandleOffset,
        y-iHandleOffset, iHandleSize, iHandleSize,
        new Cursor(Cursor.NE_RESIZE_CURSOR));
        
    selectionHandles[2] = new SelectionHandle(x+width - iHandleOffset,
        y+height - iHandleOffset, iHandleSize, iHandleSize,
        new Cursor(Cursor.SE_RESIZE_CURSOR));
      
    selectionHandles[3] = new SelectionHandle(x-iHandleOffset,
        y+height - iHandleOffset, iHandleSize, iHandleSize,
        new Cursor(Cursor.SW_RESIZE_CURSOR));

    for (int iCount=0; iCount<selectionHandles.length;iCount++){
      g.fillRect(selectionHandles[iCount].getRect().x,
          selectionHandles[iCount].getRect().y,
          selectionHandles[iCount].getRect().width,
          selectionHandles[iCount].getRect().height);
    }
  }

  @Override
  public void dehighlight(DrawPanel drawPanel){
    Graphics g = drawPanel.getGraphics();
    g.setXORMode(Color.white);
    for (int iCount=0;iCount<selectionHandles.length;iCount++){
      g.fillRect(selectionHandles[iCount].getRect().x,
          selectionHandles[iCount].getRect().y,
          selectionHandles[iCount].getRect().width,
          selectionHandles[iCount].getRect().height);
    }
    g.setPaintMode();
  }

    @Override
  public void setHandleCursor(DrawPanel drawPanel, Point p){
    if (selectionHandles != null){
      for (int i = 0; i < selectionHandles.length; i++){
        if (selectionHandles[i].getRect().contains(p)){
          drawPanel.setCursor(selectionHandles[i].getCursor());
          return;
        }
      }
    }
    drawPanel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
  }

    @Override
  public Shape getScaledBounds(float scaleFactor){
    Rectangle2D scaledBounds = new Rectangle2D.Float(
        this.x*scaleFactor-boundsDX,
        this.y*scaleFactor-boundsDY,
        this.width*scaleFactor+2*boundsDX,
        this.height*scaleFactor+2*boundsDX);
    return scaledBounds;
  }

  @Override
  public Object clone(){
    //Shallow copy the node
    CaseNode node = (CaseNode)super.clone();

    //Set cloned nodes connectedLinks to a new empty vector
    //Can not use node.connectedLinks.removeAllElements
    // because this would
    //remove the references in the original node
    node.connectedLinks = new Vector<CaseLink>();

    //The nodes clone only did a shallow copy of the content
    //and and the dialog. So we do a deep copy here
    
    //Deep copy content
    node.content = new String[content.length];
    for (int i=0; i<content.length;i++)
      node.content[i] = new String(content[i]);
    
    //WongVW
    node.panelNames = new String[content.length];
    for (int i=0; i<content.length;i++){
      node.panelNames[i] = new String(panelNames[i]);
    }

    //Create its own dialog
//    node.nodeDialog = node.createDialog();

    return node;
  }
  
//  protected void setDialog(CaseNodeDialog dialog){
//    this.nodeDialog = dialog;
//  }

//  protected void initContent() {
//    if(this.nodeDialog != null) {
//      this.content = new String[this.nodeDialog.getContentCount()];
//      this.panelNames = new String[this.nodeDialog.getContentCount()];
//      for(int i=0;i<this.nodeDialog.getContentCount();i++) {
//        content[i] = new String();
//        panelNames[i] = new String();
//      }
//    }
//  }

  public void enterData(CaseEditor pf){
    try{
      showDialog(pf);
    }
    catch(Exception exc){
      exc.printStackTrace(System.err);
    }
  }

  public void showDialog(CaseEditor pf){
    /*if (this.nodeDialog == null)
      this.nodeDialog = createDialog();

    //Set the dialog of this node
    //nodeDialog is declared in CaseNode
    if ((this.nodeDialog != null) && (this.nodeDialog instanceof CaseNodeDialog)){
      if (content != null){
        //Set the content of dialog
        for (int i=0;i<this.nodeDialog.getContentCount();i++)
          this.nodeDialog.setTextPaneContent(i, content[i]);
      }

      //Show the dialog
      this.nodeDialog.setVisible(true);
    }*/
  }

  @Deprecated
  public void closeDialog(int returnValue) {
    /*if(returnValue == JOptionPane.OK_OPTION) {
      if(content != null && nodeDialog != null)
        for(int i=0; i<content.length; i++) {
          content[i] = nodeDialog.getTextPaneContent(i);
          panelNames[i] = nodeDialog.getPanelName(i);
        }
    }*/
  }

  public String[] getContent(){
    return content;
  }

  public String getText(){
    return content[0];
  }

  public String [] getContentTitle(){
    return panelNames;
  }

  protected void fillBounds(Graphics2D g2d){
    //Get bounds
    Rectangle2D bound = (Rectangle2D)getBounds2D();

    if (bound != null){
      //Fill bounds of node to make node opaque
      g2d.setColor(Color.white);
      g2d.fill(bound);
    }
    else
      System.out.println("Bounds not defined.");
  }

  @Override
  protected void draw(Graphics2D g2d){
    if (shapes.size() > 0){
      //Draw outline
      g2d.setColor(Color.blue);
      super.draw(g2d);

      //Refresh link
      refreshLinks();
    }
    else
      System.out.println("Node outline not defined.");
  }

    @Override
  public void draw(DrawPanel dp){
    //Draw the node
    draw((Graphics2D)dp.getGraphics());
  }

    @Override
  public void draw(DrawPanel dp, Graphics g){
    //Draw the node
    draw((Graphics2D)g);
  }

  protected int getFontWidth(Graphics2D g2d, Font f){
    FontRenderContext frc = g2d.getFontRenderContext();

    //Construct a glyphvector to find a rough font width
    //using character Q (fattest char ??)
    //as a base for largest width estimate
    //This estimate is prbobly is not accurate as the "fattest"
    //character depends on the font used.
    GlyphVector gv = f.createGlyphVector(frc,"W");
    GlyphMetrics gm = gv.getGlyphMetrics(0);

    return (int)Math.round(gm.getAdvance());
  }

  //Obtain a rough estimate of max font height based
  //a line with A and g.
  @Override
  protected int getFontHeight(Graphics2D g2d, Font f) {
    FontRenderContext frc = g2d.getFontRenderContext();
    LineMetrics lm = f.getLineMetrics("Ag",frc);
    return (int)Math.round(lm.getHeight());
  }

  protected void drawText(Graphics2D g2d, String text, int x, int y) {
    int fontHeight = getFontHeight(g2d,this.currentFont);

    StringTokenizer st = new StringTokenizer(text,"\n");
    while (st.hasMoreTokens()){
      String line = st.nextToken();

      //Advance pen to baseline
      y += fontHeight;

      //Draw text
      g2d.drawString(line, x ,y);
    }//end while
  }//end of drawText(Graphics2D, String, int, int)

  public void setAlignment(int alignment) {
    this.currentAlignment = alignment;
  }

  public int getAlignment(){
    return this.currentAlignment;
  }

  protected void drawTextAlign(Graphics2D g2d, String text, int y, int gap){
    //gap is the space between the text and
    //the sides of the surrounding box
    int fontHeight = getFontHeight(g2d, this.currentFont);
    int _x = 0;

    Font font = g2d.getFont();
    FontMetrics fontMetrics = g2d.getFontMetrics(font);
    StringTokenizer st = new StringTokenizer(text,"\n");
    
    while(st.hasMoreTokens()){
      String line = st.nextToken();

      if (getAlignment() == CENTER )
        _x = this.x + (this.width-fontMetrics.stringWidth(line))/2;
      if (getAlignment() == ALIGN_LEFT )
        _x = this.x + gap;
      if (getAlignment() == ALIGN_RIGHT )
        _x= this.x + this.width-fontMetrics.stringWidth(line)-gap;

      //Advance pen to baseline
      y += fontHeight;

      //Draw text
      g2d.drawString(line, _x, y);

    }//end while
  }//end of drawTextAlign()

  // Returns maximum width given a String delimited by new line.
  protected int getMaximumWidth(Graphics2D g2d,String content){
    StringTokenizer st = new StringTokenizer(content,"\n");
    int maximumWidth = 0;

    while (st.hasMoreTokens()) {
      String line = st.nextToken();
      int currentWidth = getStringWidth(g2d,this.currentFont,line);
      if (currentWidth > maximumWidth) maximumWidth = currentWidth;
    }

    return maximumWidth;
  }

  // Returns the width (not length) of the given string.
  @Override
  protected int getStringWidth(Graphics2D g2d, Font f, String content){
    FontRenderContext frc = g2d.getFontRenderContext();
    int _width=0;

    for (int i=0; i<content.length(); i++) {
      GlyphVector gv = f.createGlyphVector(frc, new int[] {content.charAt(i)});
      GlyphMetrics gm = gv.getGlyphMetrics(0);
      _width+=(int)Math.round(gm.getAdvance());
    }

    return _width;
  }

  //Obtain a rounded up actual bounds for a paragraph of text
  protected Dimension getActualTextBounds(Graphics2D g2d, String text){
    int boxHeight=0;
    int boxWidth=0;

    Font font = g2d.getFont();
    FontMetrics fontMetrics = g2d.getFontMetrics(font);

    //int fontWidth = getFontWidth(g2d,this.currentFont);
    int fontHeight = getFontHeight(g2d,this.currentFont);

    int lineWidth=0;

    //FontRenderContext rc = g2d.getFontRenderContext();
    StringTokenizer st = new StringTokenizer(text,"\n");

    //Compute min bounding box height based on number of lines
    if (st.countTokens() == 0){
      boxHeight = fontHeight;
    }
    else
      boxHeight = st.countTokens()*fontHeight;
    
    while (st.hasMoreTokens()){
      String line = st.nextToken();
      
      lineWidth = fontMetrics.stringWidth(line);

      //Find box height and max width
      if (lineWidth > boxWidth)
        boxWidth = lineWidth;

    }//end while
    return new Dimension(boxWidth, boxHeight);
  }

  public void onMouseMove(CaseEditor caseEditor, MouseEvent evt){
    caseEditor.setDynaMsg("Node");
  }
}
